import { errorCode, errorStatus } from "@/constants/error-code";
import { API_HOST } from "@/constants/host";
import { Storage } from "@/constants/storage";
import { UploadResponse } from "@/service/type";
import Taro from "@tarojs/taro";
import { filterObj, formatQueryParams } from "./util";

interface RequestOptions extends Taro.request.Option {
  /**是否隐藏错误提示 */
  hideError?: boolean;
  /**post请求时，参数是否放在url后面, 默认不放 */
  suffix?: boolean;
  /**有些接口返回数据不标准,是否手动处理 */
  responseData?: 'normal' | 'other';
}



interface HttpRequestOptions {
  /**api请求地址 */
  host: string;
  /**超时时间 */
  timeout: number;
}


interface UploadRequest {
  /**上传文件路径 */
  filePath: string;
  /**后台接收文件的name */
  name?: string
}

export interface Response<T> {
  /**业务状态码，0：成功， 1：失败 */
  code: 0 | 1;
  /**内容 */
  data: T,
  /**错误信息 */
  msg: string
}

export interface PageResponse<T> {
  /**每页查询记录条数 */
  size: number;
  /**总记录 */
  total: number;
  /**当前页 */
  current: number;
  /**总页数 */
  pages: number;
  /**内容 */
  records: T[];
}

/**分页通用参数 */
export interface PageRequest {
  /**当前页 */
  current: number;
  /**每页显示条数 */
  size: number;

}

export type ResponseData<T, P> = P extends true ? PageResponse<T> : T

export interface ErrorWrapper<T> {
  res?: T;
  err?: Error
}

class HttpRequest {
  /**请求地址 */
  private host: string;
  /**超时时间 */
  private timeout: number;
  /**请求凭证 */
  private token: string;

  constructor(arg: HttpRequestOptions) {
    this.host = arg.host
    this.timeout = arg.timeout
    this.token = Taro.getStorageSync(Storage.ACCESS_TOKEN)
  }

  /**
   * 封装请求业务逻辑
   * @param options 
   * @returns 
   */
  public async requestCore<T, P = false>(options: RequestOptions): Promise<ErrorWrapper<ResponseData<T, P>>> {
    const { hideError, responseData = 'normal', ...reset } = options
    const params = this.generateRequestParams(reset)
    try {
      const { data, statusCode } = await Taro.request<Response<ResponseData<T, P>>>(params)
      const result = responseData === 'normal' ? data : this.formatResult<T>(data)
      this.disposeStatusCode(statusCode)
      switch (result.code) {
        case errorCode.SUCCESS:
          return {
            res: result.data
          }
      }
      console.log(result)
      throw this.newError(String(result.code), result.msg)
    } catch (error) {
      console.log(error)
      !hideError && Taro.showToast({
        title: error.message,
        icon: 'none'
      })
      if (error.name === '424') {
        /**处理登录过期情况 */
        this.redirectTo()
      }
      return { err: error }
    }
  }

  /**
   * 拼接请求参数
   * @param options 
   * @returns 
   */
  private generateRequestParams(options: RequestOptions) {
    const { suffix = false, url, header, data, ...reset } = options
    const newUrl = url.indexOf('http') !== -1 ? url : this.host + url
    return {
      ...reset,
      timeout: this.timeout,
      header: {
        Authorization: 'Bearer ' + this.token,
        ...header
      },
      data: !suffix && filterObj(data),
      url: suffix ? newUrl + formatQueryParams(data) : newUrl
    }
  }

  /**
   * 生成错误对象
   * @param code 
   * @param msg 
   * @returns 
   */
  private newError(code: string, msg: string) {
    const error = new Error()
    error.name = code
    error.message = msg
    return error
  }

  /**
   * 处理错误状态码，Taro不会捕获网络错误码
   * @param code 
   */
  private disposeStatusCode(code: number) {
    const message = errorStatus[code]
    if (message) {
      throw this.newError(String(code), message)
    }
  }

  /**登录过期重定向 */
  private redirectTo() {
    this.removeToken()
    //拿到当前页面路径及参数
    const pages = Taro.getCurrentPages()
    const { route, options } = pages[pages.length - 1]
    if (route !== 'pages/login/index') {
      Taro.reLaunch({
        url: `/pages/login/index?redirectUrl=${route}&params=${encodeURIComponent(encodeURIComponent(JSON.stringify(options)))}`
      })
    }

  }

  /**处理不标准的返回值 */
  private formatResult<T>(data: any) {
    return {
      code: 0,
      data: data as T,
      msg: ''
    } as any
  }

  /**
   * post请求
   * @param url 
   * @param data 
   * @param options 
   * @returns 
   */
  public post<T, P = false>(url: string, data?: Record<string, any>, options?: Omit<RequestOptions, 'method' | 'url' | 'data'>) {
    return this.requestCore<T, P>({
      url,
      data: data || {},
      method: 'POST',
      ...options
    })
  }

  /**
   * get请求
   * @param url 
   * @param data 
   * @param options 
   * @returns 
   */
  public get<T, P = false>(url: string, data?: Record<string, any>, options?: Omit<RequestOptions, 'method' | 'url' | 'data'>) {
    return this.requestCore<T, P>({
      url,
      data: data || {},
      method: 'GET',
      ...options
    })
  }
  /**
   * put请求
   * @param url 
   * @param data 
   * @param options 
   * @returns 
   */
  public put<T, P = false>(url: string, data?: Record<string, any>, options?: Omit<RequestOptions, 'method' | 'url' | 'data'>) {
    return this.requestCore<T, P>({
      url,
      data: data || {},
      method: 'PUT',
      ...options
    })
  }

  /**
   * delete请求
   * @param url 
   * @param data 
   * @param options 
   * @returns 
   */
  public delete<T, P = false>(url: string, data?: Record<string, any>, options?: Omit<RequestOptions, 'method' | 'url' | 'data'>) {
    return this.requestCore<T, P>({
      url,
      data: data || {},
      method: 'DELETE',
      ...options
    })
  }

  /**文件上传 */
  public async upload(url: string, data: UploadRequest, options?: Omit<Taro.uploadFile.Option, 'url' | 'filePath' | 'name'>): Promise<ErrorWrapper<Response<UploadResponse>>> {
    const { filePath, name = 'file' } = data
    try {
      const { data, statusCode } = await Taro.uploadFile({
        ...options,
        header: {
          Authorization: 'Bearer ' + this.token
        },
        url: API_HOST + url,
        filePath,
        name
      })
      this.disposeStatusCode(statusCode)
      // data => "{"code":1,"msg":"token 过期","data":"Full authentication is required to access this resource"}"
      const result = JSON.parse(data) as Response<UploadResponse>
      switch (result.code) {
        case errorCode.SUCCESS:
          return {
            res: result
          }
      }
      throw this.newError(String(result.code), result.msg)
    } catch (error) {
      Taro.showToast({
        title: error.message,
        icon: 'none'
      })
      if (error.name === '424') {
        /**处理登录过期情况 */
        this.redirectTo()
      }
      return { err: error }
    }
  }

  /**设置token */
  public setToken(token: string) {
    this.token = token
    Taro.setStorageSync(Storage.ACCESS_TOKEN, token)
  }

  /**删除token */
  public removeToken() {
    this.token = ''
    Taro.clearStorageSync()
  }
}


export default new HttpRequest({
  host: API_HOST,
  timeout: 20000
})